package sadi.server;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*; 
import sadi.util.*;

public class BJServer extends JFrame
{
	//Constants
	private final int minClients = 2;
	private final int maxClients = 6;
	
	//GUI Instance Variables
	private JTextArea log;
	private JScrollPane scrollLog;
	private JButton resetlog;
	
	//Server Instance Variables
	private ServerSocket serverSocket;
	
	public static void main(String [] args)
	{
		BJServer server = new BJServer();
	}
	
	public BJServer()
	{
		//Set Up GUI
		log = new JTextArea();
		scrollLog = new JScrollPane(log);
	    add(scrollLog, BorderLayout.CENTER);
	    
	    resetlog = new JButton("Clear Server Log");
	    resetlog.addActionListener	(
	    								new ActionListener()
	    								{
	    									public void actionPerformed(ActionEvent e)
	    									{
	    										int confirm = JOptionPane.showConfirmDialog(null, 
	    													  "Are you sure you want to clear the log?", "Clear Server Log", JOptionPane.YES_NO_OPTION);

	    										if(confirm == JOptionPane.YES_OPTION)
	    											log.setText("");
	    									}
	    								}
	    							);
	    add(resetlog, BorderLayout.SOUTH);
	    
	    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	    setSize(500, 500);
	    setTitle("BJServer");
	    setVisible(true);
	    
	    try
	    {
		 	//Set Up Server
		    serverSocket = new ServerSocket(BJConstants.PORT);
		    appendLog(true, "BJServer started at socket " + BJConstants.PORT + ".\n\n");
		    BJSession session = new BJSession(this, minClients, maxClients);
		    
		    //Wait for Minimum Amount of Player to Join
		    //Accept Connection & Get Username
		    for(int i=0; i<minClients; i++)	
		    	session.initSession(serverSocket.accept());
		    
		    //Start BlackJack Logic Handling Session
		    appendLog(true, "Starting a new session...\n");
		    session.start();
		    
		    //Continue to Listen for New Connections
		    while(true)
		    {
		    	if(session.joinSession(serverSocket.accept()) && session.getTotalClients() <= 2)
		    		session.interrupt();
		    }
	    }
	    catch(IOException e)
	    {
	    	e.printStackTrace();
	    }
	}
	
	//Helper Functions to Write Log Messages to GUI TextArea
	public void appendLog(boolean hasDate, Object message)
	{
		//Add Date/Time prefix to message
		String prefix = (hasDate)? new Date() + ": " : "";		
		log.append(prefix + message);
	}
}

class BJSession extends Thread implements BJConstants
{
	//Instance Variables
	private int minClients, maxClients,
				totalClients, //Clients connected to the current game including observers
				clientID; //Client ID (Cumulative Over TIme)
	private int trialNum, gameNum;
	private boolean isObservable;
	private boolean hasDealerInit[]; //observers has dealer's initial card
	private BJServer server;
	private CardsCollection cards;
	private Dealer dealer;
	private String dealerCard;
	private BlackJackers clients[], //Clients connected to the game including observers
						 players[], //Players = Active(Playing) Clients + Dealer
						 observers[];
	private Map<String, Integer> trialRank, gameRank;
	
	public BJSession(BJServer server, int minClients, int maxClients)
	{
		this.server = server;
		this.minClients = minClients;
		this.maxClients = maxClients;
		totalClients = 0;
		clientID = 1;
		trialNum = 1;
		gameNum = 1;
		isObservable = true;
		
		cards = new CardsCollection();
		dealer = new Dealer(NEWGAME_WAIT);
		clients = new BlackJackers[maxClients];
		trialRank = new LinkedHashMap<String, Integer>();
		gameRank = new LinkedHashMap<String, Integer>();
		gameRank.put(dealer.getUsername(), 0);
		
		hasDealerInit = new boolean[maxClients - minClients];
		for(int i=0; i<hasDealerInit.length; i++)
			hasDealerInit[i] = false;
		observers = new BlackJackers[maxClients - minClients];
	}
	
	
	
	//Get Total Clients Count in Session
	public int getTotalClients()
	{
		return totalClients;
	}	

	//Increment Score in Ranking
	private void updateRanking(Map<String, Integer> map, String winner)
	{
		if(map.containsKey(winner))
		{
			int newScore = map.get(winner) + 1;
			map.put(winner, newScore);
		}
	}
	
	
	
	/*************** METHODS TO JOIN SESSION FROM SERVER ***************/
	
	//Session Accepting First n Clients, Where n = minClients
	public boolean initSession(Socket socket)
	{
		if(totalClients < minClients)
		{
			try
			{
				DataInputStream dis = new DataInputStream(socket.getInputStream());
				String username = dis.readUTF() + " (C" + clientID + ")";
				String ipAddress = socket.getInetAddress().getHostAddress();
			
				//Init New Player Object & Update Counters
				Player player = new Player(socket, username, NEWGAME_WAIT);
				player.sendStatus();
				clients[totalClients] = player;
				gameRank.put(username, 0);
				totalClients++;
				clientID++;
			
				//Update Server Log
				int need = minClients - totalClients;
				server.appendLog(true, username + " joins the game via " + ipAddress + ".\n");
				server.appendLog(false, "Waiting for " + need + " more player(s).\n");
				return true;
			}
			catch(IOException e)
			{
				System.out.println("Problem with session initialization.");
				//e.printStackTrace();
				return false;
			}
		}
		else
			return false;
	}
	
	//Session Accepting The Remaining Clients
	//Re-divert To initSession() For First n Clients, Where n = minClients
	//Auto Close Connection When Session Reached Maximum Player Limit
	public boolean joinSession(Socket socket)
	{
		if(totalClients < minClients)
		{
			initSession(socket);
			return true;
		}
		else if((totalClients < maxClients) && isObservable)
		{
			try
			{
				DataInputStream dis = new DataInputStream(socket.getInputStream());
				String username = dis.readUTF() + " (C" + clientID + ")";
				String ipAddress = socket.getInetAddress().getHostAddress();
				
				//Init New Player Object & Update Counters
				Player player = new Player(socket, username, NEWGAME_OBSERVE);
				player.sendStatus();
				clients[totalClients] = player;
				observers[totalClients - minClients] = player;  //totalClients - minClients = index to observer array
				hasDealerInit[totalClients - minClients] = true;//totalClients - minClients = index to observer array
				gameRank.put(username, 0);
				totalClients++;
				clientID++;
				
				//Update Server Log
				server.appendLog(true, username + " joins the game via " + ipAddress + ".\n");
				
				//Send Ranking Info & Dealer's Face Card
				player.sendRanking(gameRank);
				player.sendRanking(trialRank);
				player.sendDealerInfo(dealerCard);
				player.sendTrialInfo(trialNum);
				
				return true;
			}
			catch(IOException e)
			{
				System.out.println("Problem with joining a session.");
				//e.printStackTrace();
				return false;
			}
		}
		else
		{
			try
			{
				socket.close();
			}
			catch (IOException e)
			{
				e.printStackTrace();
			}
			
			return false;
		}
	}
	
	
	
	/*************** INHERITED METHODS ***************/
	
	//Thread Run Method Inherited From Thread Class
	public void run()
	{
		while(true)
		{
			//Start A New Game
			if(trialNum == 1)
			{
				//Check For Enough Minimum Clients
				if(totalClients < minClients)
				{
					int need = minClients - totalClients;
					server.appendLog(true, "Waiting for " + need + " more player(s).\n");
					
					while(totalClients < minClients)
					{
						try
						{
							sleep(60000);
						}
						catch(InterruptedException e)
						{
							need = minClients - totalClients;
							server.appendLog(false, "Waiting for " + need + " more player(s).\n");
						}
					}
				}		
				
				
				//Init All Active Clients For New Game
				players = new BlackJackers[totalClients + 1];
				trialRank.clear();
				server.appendLog(false, "\n");
				server.appendLog(true, "Starting Game #" + gameNum + " : " + (players.length-1) + " Client Players.\n\n");				
				for(int i=0, j=0; i<players.length - 1; i++)
				{					
					Player player = (Player)clients[i];
					players[i] = clients[i];
					trialRank.put(player.getUsername(), 0);
					
					//Notify Observer That They've Entered The Game
					if(player.getStatus() == NEWGAME_OBSERVE)
					{
						player.setStatus(NEWGAME_WAIT);
						player.sendStatus();
					}
					
					j++;
				}
				
				//Set Dealer As The Last Player
				players[players.length - 1] = dealer;
				trialRank.put(dealer.getUsername(), 0);
			}
			
			
			
			//Reset Card Collection & Draw 2 Random Cards
			isObservable = true;
			cards.resetStatus();
			for(int i=0; i<players.length; i++)
			{
				String card1 = cards.drawRandomCard();
				String card2 = cards.drawRandomCard();
				server.appendLog(true, players[i].getUsername() + " drew " + card1 + " & " + card2 + ".\n");
				players[i].initGame(cards, card1, card2);
			}
			
			//Send Dealer Face Card & Signal Start Game
			dealerCard = dealer.showInitCard(cards, true);
			for(int i=0; i<players.length; i++)
				players[i].startGame(dealerCard);
			for(int i=players.length-1-minClients; i<observers.length; i++) //Send To Observers
			{
				if(!hasDealerInit[i] && observers[i] != null)
					((Player)observers[i]).sendDealerInfo(dealerCard);
			}
			
			
			
			//Active Clients Play According To Turns
			for(int i=0; i<players.length-1; i++)
			{
				server.appendLog(true, players[i].getUsername() + "'s Turn...\n");
				while(players[i].hasNextMove())
				{
					//Request Next Move From Players
					int response = players[i].nextMove();
					
					//Do Valid Client Response
					if(response == ACT_HIT)
					{
						String tempCard =  cards.drawRandomCard();
						players[i].hit(cards, tempCard);
						server.appendLog(true, players[i].getUsername() + " drew " + tempCard + ".\n");
					}
					else if(response == ACT_STAND)
					{
						players[i].stand();
						server.appendLog(true, players[i].getUsername() + " stand.\n");
					}
					else if(response == ACT_SPLIT)
					{
						players[i].split(cards);
						server.appendLog(true, players[i].getUsername() + " splitted.\n");
					}
					else
						server.appendLog(true, players[i].getUsername() + " withdrawn.\n");
				}
			}

			//Show The Remaining Dealer Card To All Clients
			//Block Observer Mode To Avoid Problems
			isObservable = false;
			String dealerTemp = dealer.showInitCard(cards, false);
			for(int i=0; i<totalClients; i++)
				((Player)clients[i]).sendDealerInfo(dealerTemp, true);
			
			//Dealer Plays Last
			server.appendLog(true, dealer.getUsername() + "'s Turn...\n");
			while(dealer.hasNextMove())
			{
				//Request Next Move From Dealer
				int response = dealer.nextMove();
				
				if(response == ACT_HIT)
				{
					dealerTemp = cards.drawRandomCard();
					dealer.hit(cards, dealerTemp);
					
					//Send Newly Drawn Card To All Connected Clients
					for(int i=0; i<totalClients; i++)
						((Player)clients[i]).sendDealerInfo(dealerTemp, true);
				}
				else //ACT_STAND
					dealer.stand();
			}
			
			//Send Dealer's Final Total To All Connected Clients
			server.appendLog(true, "Dealers Best Chance " + dealer.getBestChance() + "\n");
			server.appendLog(true, "Dealers Current Chance " + dealer.getCurrentChance() + "\n");
			dealerTemp = Integer.toString(dealer.getCurrentChance()); //doesn't matter even if it burst
			for(int i=0; i<totalClients; i++)
				((Player)clients[i]).sendDealerInfo(dealerTemp, false);
			
			
			
			//Search For Winner Of The Trial
			boolean trialMultiWin = false; //Multiple "winner" with the same score: DRAW
			int trialBestScore = 0;
			String trialWinner = "";
			
			for(int i=0; i<players.length; i++)
			{
				int temp = players[i].getBestChance();
				
				if(trialBestScore < temp)
				{
					trialBestScore = temp;
					trialWinner = players[i].getUsername();
					trialMultiWin = false;
				}
				else if(trialBestScore == temp)
					trialMultiWin = true;
				
				//Reset Counters & Values
				players[i].resetGame();
			}
			
			//Reset Winner If Draw
			//Update Trial Rank If Not Draw
			if(trialMultiWin)
				trialWinner = STATUS_DRAW;
			else
				updateRanking(trialRank, trialWinner);

			server.appendLog(true, "Searching For Trial Winner: " + trialWinner + ".\n");
			
			
			
			//Update Final Game Rank At The Last Trial
			if(trialNum == TRIAL_PERGAME)
			{
				//Search For Winner Of The Game
				Set<String> keys = trialRank.keySet();
				Iterator<String> ite = keys.iterator();
				boolean gameMultiWin = false; //Multiple "winner" with the same score: DRAW
				int gameBestScore = 0;
				String gameWinner = "";
				
				while(ite.hasNext())
				{
					String key = ite.next();
					int temp = trialRank.get(key);
					
					if(gameBestScore < temp)
					{
						gameWinner = key;
						gameBestScore = temp;
						gameMultiWin = false;
					}
					else if(gameBestScore == temp)
						gameMultiWin = true;
				}
				
				//Reset Winner If Draw
				//Update Game Rank If Not Draw
				if(gameMultiWin)
					gameWinner = "Draw";
				else
					updateRanking(gameRank, gameWinner);
				
				server.appendLog(true, "Searching For Game Winner: " + gameWinner + ".\n");
			}
			
			
			
			//Send Winner Username & Updated Ranking To All Connected Clients
			server.appendLog(true, "Sending Rankings...\n");
			for(int i=0; i<totalClients; i++)
			{
				Player client = (Player)clients[i];
				client.sendWinnerInfo(trialWinner, trialBestScore);
				client.sendRanking(gameRank);
				client.sendRanking(trialRank);
			}
			
			//Clear Observer Dealer Init Flag
			for(int i=0; i<hasDealerInit.length; i++)
				hasDealerInit[i] = false;
			
			//Pause Game
			try
			{
				server.appendLog(false, "\n");
				server.appendLog(true, "Game #" + gameNum + " Trial #" + trialNum + " Paused for Rankings View!\n\n");
				sleep(BJConstants.PAUSE);
			}
			catch(InterruptedException e)
			{
				e.printStackTrace();
			}
			
			
			//Update Counter
			trialNum++;			
			
			//Cleaning Up At The Last Trial Of The Game
			if(trialNum > TRIAL_PERGAME)
			{
				//Update Counters
				trialNum = 1;
				gameNum++;				
				
				//Prune Inactive Clients			
				server.appendLog(true, "Pruning Inactive Clients: ");
				int totalActive = 0;
				int tempTotal = totalClients;
				for(int i=0; i<tempTotal; i++)
				{
					boolean isActive = ((Player)clients[i]).isActive();
					if(isActive)
					{					
						if(i > totalActive)
							clients[totalActive] = clients[i];
						
						totalActive++;
					}
				}
				
				//Reset Client Slots To Empty As Necessary
				server.appendLog(false, "Closed " + (tempTotal-totalActive) + " Socket(s).\n\n");
				for(int i=totalActive; i<tempTotal; i++)
				{
					clients[i] = null;
					totalClients--;
				}
				
				//Reset Observer Slots
				for(int i=0; i<observers.length; i++)
					observers[i] = null;
			}

		}
	}
}